-
Notifications
You must be signed in to change notification settings - Fork 32
Variable definitions validation #186
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Variable definitions validation #186
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks! This looks very much along the lines that we spoke about, and is great work for a first serious Haskell PR.
As requested, I've tried to provide as much feedback as I can. The things marked "Nit" are ultimately unimportant things that I cannot stop myself from caring about.
src/GraphQL/Internal/Schema.hs
Outdated
@@ -58,6 +60,9 @@ newtype Schema = Schema (Map Name TypeDefinition) deriving (Eq, Ord, Show) | |||
makeSchema :: ObjectTypeDefinition -> Schema | |||
makeSchema = Schema . getDefinedTypes | |||
|
|||
emptySchema :: Schema |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please add a doc comment. Ideally it should say what it's used for, or why you might want to use it.
src/GraphQL/Internal/Schema.hs
Outdated
|
||
-- | Convert the given TypeDefinition to an InputTypeDefinition if it's a valid InputTypeDefinition | ||
-- (because InputTypeDefinition is a subset of TypeDefinition) | ||
-- see <http://facebook.github.io/graphql/June2018/#sec-Input-and-Output-Types> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please put single quotes around TypeDefinition
etc. This makes Haddock render them as links.
src/GraphQL/Internal/Validation.hs
Outdated
@@ -174,7 +181,7 @@ validateOperations schema fragments ops = do | |||
traverse validateNode deduped | |||
where | |||
validateNode (operationType, AST.Node _ vars directives ss) = | |||
operationType <$> lift (validateVariableDefinitions vars) | |||
operationType <$> lift ((validateVariableDefinitions schema) vars) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think the inner parentheses are necessary.
src/GraphQL/Internal/Validation.hs
Outdated
@@ -626,7 +633,8 @@ validateArguments args = Arguments <$> mapErrors DuplicateArgument (makeMap [(na | |||
data VariableDefinition | |||
= VariableDefinition | |||
{ variable :: Variable -- ^ The name of the variable | |||
, variableType :: AST.GType -- ^ The type of the variable | |||
-- , variableType :: AST.GType -- ^ The type of the variable |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please remove all commented out code.
src/GraphQL/Internal/Validation.hs
Outdated
validateVariableDefinition schema (AST.VariableDefinition var varType value) = | ||
case validateTypeAssertion schema var varType of | ||
Left e -> throwE e | ||
Right t -> VariableDefinition var t <$> traverse validateDefaultValue value |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Couple of things here.
First, I think this code could be written the same way as:
do
t <- validateTypeAssertion schema var varType
VariableDefinition var t <$> traverse validateDefaultValue value
since that's the way the Validation
monad behaves.
But, you probably don't want to use the monadic behaviour. Why? Because writing this as "first validateTypeAssertion
, then validateDefaultValue
" implicitly communicates that validateDefaultValue
depends on validateTypeAssertion
. But of course it doesn't.
From a user experience point of view, this means if there's a problem with the type assertion and a problem with the default value, they will only get the error from the type assertion. This is not ideal.
Instead, you want to use the applicative behaviour. e.g.
VariableDefinition var <$> validateTypeAssertion schema var varType <*> traverse validateDefaultValue value
This clearly shows that the two are independent, and the Validation
applicative instance will make sure both errors are included (if there happen to be errors from both).
Does this make sense?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes. Not sure I thoroughly grasp the entire concepts of Applicative and Monad, but as far as this example is concerned I think I understand.
src/GraphQL/Internal/Validation.hs
Outdated
| tname == getName GID = Right (BuiltinInputType GID) | ||
| otherwise = Left (VariableTypeNotFound var tname) | ||
|
||
-- | simple translation between ast annotation types and schema annotation types |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: capital first letter, capitalize "AST" and end with a full stop.
src/GraphQL/Internal/Validation.hs
Outdated
astAnnotationToSchemaAnnotation gtype schematn = | ||
case gtype of | ||
AST.TypeNamed _ -> TypeNamed schematn | ||
AST.TypeList (AST.ListType asttn) -> astAnnotationToSchemaAnnotation asttn schematn |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
schemaTypeName
, astTypeName
src/GraphQL/Internal/Validation.hs
Outdated
astAnnotationToSchemaAnnotation gtype schematn = | ||
case gtype of | ||
AST.TypeNamed _ -> TypeNamed schematn | ||
AST.TypeList (AST.ListType asttn) -> astAnnotationToSchemaAnnotation asttn schematn |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks wrong. The annotated type of "list of int" should be "list of int", not "int".
src/GraphQL/Internal/Validation.hs
Outdated
|
||
-- | validate a variable type which has no type definition (either builtin or not in the schema) | ||
validateVariableTypeBuiltin :: Variable -> Name -> Either ValidationError InputType | ||
validateVariableTypeBuiltin var tname |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why does this take a Variable
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It takes a variable name because it enables us to give the variable name in the error message (if the given type is not a builtin type, which is the error message if we give a random type name for instance). I do believe this is valuable.
tests/ValidationTests.hs
Outdated
@@ -27,11 +29,11 @@ someName = "name" | |||
|
|||
dog :: Name | |||
dog = "dog" | |||
|
|||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: Please don't add unnecessary whitespace.
@jml, Now I think I'm done here, for some reason the tests are marked as failed on CircleCI, but there isn't any error at any stage of the pipeline and the coverage increased everywhere:
I'm still open to other changes though if things are not right yet. |
@theobat Thanks, I'll take a look now. To fix the coverage error, you need to update the numbers in the script to match the new, better numbers: https://github.com/haskell-graphql/graphql-api/blob/master/scripts/hpc-ratchet#L37-L43 (Edit: correct link) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks great, thanks! I'm super excited to see the improved test coverage.
One minor comment.
tests/EndToEndTests.hs
Outdated
, "errors" .= | ||
[ | ||
object | ||
-- TODO: cf previous test case |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure I could do this todo. Please either do it or expand on it so that someone who's not you could do it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So this is just a reference to the one above that I did not write.
-- TODO: This error message is pretty bad. We should define
-- a typeclass for client-friendly "Show" (separate from
-- actual Show which remains extremely useful for debugging)
-- and use that when including values in error messages.
Now, while I understand both the idea and how to do it (I think...), it seems like a lot of work for this PR. Since both error messages are pretty close, I thought it would be better to add the TODO reference to the other one, but now that I'm reading it on github, it seems rather unhelpful. I'm going to simply remove it and let the original TODO as it is
@jml There we are ! Thanks for your reviews. As a side note, it seems like every guard containing an otherwise statement is counted as "always true" by hpc which means increasing the number of otherwise might force us to decrease coverage..
Next step is a PR with the actual Aeson.Object to Value translation. I won't address it before some time though. |
Yeah, that's something I really would like to fix in hpc (or with some kind of tooling), rather than compromising on coverage. The Haskell testing ecosystem needs to get better. |
Thanks! |
This PR is a required step towards the resolution of #145. It improves the validation of type declaration in variable definitions. For instance it validates the fact that when a document contains $myVariable: MyVar, then the MyVar type actually exists in the schema and it's an InputType.
Remaining tasks:
PS: this is my first "real" attempt to write haskell so please, any insight would be greatly appreciated.
Edit: I added DefinesTypes instances on arguments because failing tests were simply not finding their argument types in the schema (schema in the sense of what is returned by makeSchema)